﻿/*
 * LdRequestDocument
 * 
 * initial implementation : 26.6.2013
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see cz.uhul.DSMLv2.txt
 */
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Linq;
using System.IO;
using System.Text;
//using System.ServiceModel;
//using System.ServiceModel.Channels;


namespace cz.uhul.DSMLv2
{



  /// <summary>
  /// The LdRequestDocument class enables
  /// you to build a DSML request payload, such as LdAddRequest,
  /// LdModifyRequest, or LdSearchRequest.
  /// It contains zero or more LdRequest
  /// derived objects. The user may add, delete, modify, and enumerate the LdRequest
  /// before sending to the server. LdRequestDocument
  /// can be used with any DSML Binding classes to transport the document, such
  /// as LdConnection.
  /// </summary>

  [XmlRoot(ElementName = "batchRequest", Namespace = "urn:oasis:names:tc:DSML:2:0:core")]
  //[XmlInclude(typeof(LdAuthRequest))]
  [XmlInclude(typeof(LdAddRequest))]
  [XmlInclude(typeof(LdDeleteRequest))]
  [XmlInclude(typeof(LdModifyRequest))]
  [XmlInclude(typeof(LdSearchRequest))]
  [XmlInclude(typeof(LdCompareRequest))]
  [XmlInclude(typeof(LdModifyDnRequest))]
  //[MessageContract(WrapperName = "batchRequest")]
  public class LdRequestDocument : LdDocument
  {

    ///<Summary>
    /// The LdRequestDocument.#ctor() constructor
    /// creates an instance of the LdRequestDocument
    /// class.
    ///</Summary>
    public LdRequestDocument()
    {
      //AddRequests = new ObservableCollection<LdAddRequest>();
      //ModifyRequests = new ObservableCollection<LdModifyRequest>();
    }





    ///<Summary>
    /// The LdRequestDocument.DocumentProcessing
    /// property contains one of the values of LdDocumentProcessing
    /// that specifies how the elements of the collection are processed.
    ///</Summary>
    /// <Returns>
    /// One of the values of LdDocumentProcessing
    /// that specifies how the elements of the collection are processed.
    ///</Returns>
    /// <Exceptions>
    ///   System.ComponentModel.InvalidEnumArgumentException:
    /// value is an invalid enumeration value.
    /// </Exceptions>
    ///</Summary>
    [XmlAttribute(AttributeName = "processing")]
    public LdDocumentProcessing DocumentProcessing { get; set; }






    ///<Summary>
    /// The System.DirectoryServices.Protocols.DsmlRequestDocument.ErrorProcessing
    /// property contains one of the values of System.DirectoryServices.Protocols.DsmlErrorProcessing
    /// that specifies how errors are processed.
    ///</Summary>
    /// Returns:
    /// One of the values of System.DirectoryServices.Protocols.DsmlErrorProcessing
    /// that specifies how errors are processed.
    ///
    /// Exceptions:
    ///   System.ComponentModel.InvalidEnumArgumentException:
    /// value is an invalid enumeration value.
    [XmlAttribute(AttributeName = "onError")]
    public LdErrorProcessingEnum ErrorProcessing { get; set; }





    /////<Summary>
    ///// The System.DirectoryServices.Protocols.DsmlRequestDocument.RequestId property
    ///// contains the RequestID associated with the System.DirectoryServices.Protocols.DsmlRequestDocument.
    /////
    ///// Returns:
    ///// The RequestID associated with the System.DirectoryServices.Protocols.DsmlRequestDocument.
    //[XmlAttributeAttribute(AttributeName = "requestId")]
    //public string RequestId { get; set; }






    ///<Summary>
    /// The System.DirectoryServices.Protocols.DsmlRequestDocument.ResponseOrder
    /// property contains one of the values of System.DirectoryServices.Protocols.DsmlResponseOrder
    /// that specifies the order of the response.
    ///
    /// Returns:
    /// One of the values of System.DirectoryServices.Protocols.DsmlResponseOrder
    /// that specifies the order of the response.
    ///
    /// Exceptions:
    ///   System.ComponentModel.InvalidEnumArgumentException:
    /// value is an invalid enumeration value.
    [XmlAttribute(AttributeName = "responseOrder")]
    public LdResponseOrderEnum ResponseOrder { get; set; }





    [XmlElement(ElementName = "authRequest", /*Order = 0,*/ Form = XmlSchemaForm.None, IsNullable = false)]
    public virtual LdAuthRequest AuthRequest { get; set; }





    //[XmlArray(ElementName = "modifyRequest")]
    [XmlElement(ElementName = "modifyRequest", Form = XmlSchemaForm.None, IsNullable = true, Type = typeof(LdModifyRequest))]
    //public LdModifyRequestCollection ModifyRequests { get; set; }
    public LdRequestsCollection ModifyRequests { get; set; }





    [XmlElement(ElementName = "searchRequest", Form = XmlSchemaForm.None, IsNullable = true, Type = typeof(LdSearchRequest))]
    public LdRequestsCollection SearchRequests { get; set; }




    //[XmlArray(ElementName = "addRequest")]
    [XmlElement(ElementName = "addRequest", Form = XmlSchemaForm.None, IsNullable = true, Type = typeof(LdAddRequest))]
    //public LdAddRequestCollection AddRequests { get; set; }
    public LdRequestsCollection AddRequests { get; set; }





    [XmlElement(ElementName = "delRequest", Form = XmlSchemaForm.None, IsNullable = true, Type = typeof(LdDeleteRequest))]
    public LdRequestsCollection DeleteRequests { get; set; }





    [XmlElement(ElementName = "modDNRequest", Form = XmlSchemaForm.None, IsNullable = true, Type = typeof(LdModifyDnRequest))]
    public LdRequestsCollection ModifyDnRequests { get; set; }





    [XmlElement(ElementName = "compareRequest", Form = XmlSchemaForm.None, IsNullable = true, Type = typeof(LdCompareRequest))]
    public LdRequestsCollection CompareRequests { get; set; }





    [XmlElement(ElementName = "extendedRequest", Form = XmlSchemaForm.None, IsNullable = true, Type = typeof(LdExtendedRequest))]
    public LdExtendedRequestCollection ExtendedRequests { get; set; }





    [XmlIgnore]
    public LdRequestsCollection Requests
    {
      get
      {
        LdRequestsCollection tmp = new LdRequestsCollection();

        if (this.AddRequests != null)
          foreach (LdAddRequest r in this.AddRequests)
            tmp.Add(r);

        if (this.ModifyRequests != null)
          foreach (LdModifyRequest r in this.ModifyRequests)
            tmp.Add(r);

        if (this.SearchRequests != null)
          foreach (LdSearchRequest r in this.SearchRequests)
            tmp.Add(r);

        if (this.DeleteRequests != null)
          foreach (LdDeleteRequest r in this.DeleteRequests)
            tmp.Add(r);

        if (this.ModifyDnRequests != null)
          foreach (LdModifyDnRequest r in this.ModifyDnRequests)
            tmp.Add(r);

        if (this.CompareRequests != null)
          foreach (LdCompareRequest r in this.CompareRequests)
            tmp.Add(r);

        if (this.ExtendedRequests != null)
          foreach (LdExtendedRequest r in this.ExtendedRequests)
            tmp.Add(r);

        return tmp;
      }
    }




    public XmlDocument ToXml()
    {
      XmlDocument outDoc = new XmlDocument();
      XPathNavigator nav = outDoc.CreateNavigator();

      XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(nav.AppendChild());

      //XmlSerializerNamespaces xnameSpace = new XmlSerializerNamespaces();
      //xnameSpace.Add("", "");

      XmlSerializer xs = new XmlSerializer(typeof(LdRequestDocument), "urn:oasis:names:tc:DSML:2:0:core");
      //xs.Serialize(writer, this, xnameSpace);
      xs.Serialize(writer, this);

      writer.Close();

      //// Memorise and remove the element we want
      //XmlNode infoNode = outDoc.FirstChild;
      //outDoc.RemoveChild(infoNode);

      //// Move into a root <detail> element
      //var rootNode = outDoc.AppendChild(outDoc.CreateNode(XmlNodeType.Element, "detail", ""));
      //rootNode.AppendChild(infoNode);

      return outDoc;
    }






    //public Message ToMessage(MessageVersion ver, string op)
    //{
    //  return Message.CreateMessage(ver, op, new XmlNodeReader(ToXml()));
    //}




    public XmlReader ToXmlReader()
    {
      return new XmlNodeReader(this.ToXml());
    }




    /// <summary>
    /// parses DSML document from input <c>XmlDocument</c>
    /// </summary>
    /// <param name="doc"></param>
    /// <returns></returns>
    public static LdRequestDocument Parse(object data)
    {
      XmlDocument xdoc;

      if (data is XmlDocument)
        return LdRequestDocument.Parse((XmlDocument)data);

      else if (data is string)
      {
        xdoc = new XmlDocument();
        xdoc.LoadXml((string)data);
        return LdRequestDocument.Parse(xdoc);
      }
      else if (data is XmlElement)
        return LdRequestDocument.Parse((XmlElement)data);

      //else if (doc is Message)
      //{
      //  xdoc = new XmlDocument();
      //  XPathNavigator nav = xdoc.CreateNavigator();

      //  XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(nav.AppendChild());
      //  ((Message)doc).WriteBodyContents(writer);

      //  writer.Close();

      //  return LdRequestDocument.Parse(xdoc);
      //}
      else if (data is XmlDictionaryReader)
      {
        return LdRequestDocument.Parse((XmlReader)data);
      }
      else
        throw new LdException("LdRequestDocument.Parse error, invalid input of type:" + data.GetType().ToString());

    }



    /// <summary>
    /// parses DSML document from input <c>XmlDocument</c>
    /// </summary>
    /// <param name="doc"></param>
    /// <returns></returns>
    public static LdRequestDocument Parse(XmlElement docElm)
    {

      //validateDSML(doc);

      //System.Xml.Linq.XDocument xdoc = System.Xml.Linq.XDocument.  Load(@"test.xml");
      //var tabs = xdoc.Descendants("Tab")
      //              .OrderBy(t => Convert.ToInt32(t.Attribute("Order").Value))
      //              .ToList();

      XmlSerializer srl = new XmlSerializer(typeof(LdRequestDocument), "urn:oasis:names:tc:DSML:2:0:core");

      XmlNodeReader reader = new XmlNodeReader(docElm);
      //srl.UnknownAttribute += new XmlAttributeEventHandler(i1);
      //srl.UnknownElement += new XmlElementEventHandler(i2); 
      //srl.UnknownNode += new XmlNodeEventHandler(i3);
      //srl.UnreferencedObject += new UnreferencedObjectEventHandler(i4);

      if (!srl.CanDeserialize(reader))
        throw new ArgumentException("LdRequestDocument.Parse:errror parsing input xml");

      LdRequestDocument tmpDoc = (LdRequestDocument)srl.Deserialize(reader);

      if (tmpDoc.ResponseOrder == LdResponseOrderEnum.Sequential)
        throw new LdException("sequential processing currentry unsupported, please use Unordered");

      return tmpDoc;

    }





    /// <summary>
    /// parses DSML document from input <c>XmlDocument</c>
    /// </summary>
    /// <param name="doc"></param>
    /// <returns></returns>
    public static LdRequestDocument Parse(XmlDocument doc)
    {

      //validateDSML(doc);

      //System.Xml.Linq.XDocument xdoc = System.Xml.Linq.XDocument.  Load(@"test.xml");
      //var tabs = xdoc.Descendants("Tab")
      //              .OrderBy(t => Convert.ToInt32(t.Attribute("Order").Value))
      //              .ToList();

      //Type[] tp = { typeof(LdSearchRequest) };

      XmlSerializer srl = new XmlSerializer(typeof(LdRequestDocument), "urn:oasis:names:tc:DSML:2:0:core");

      XmlNodeReader reader = new XmlNodeReader(doc.DocumentElement);
      //srl.UnknownAttribute += new XmlAttributeEventHandler(i1);
      //srl.UnknownElement += new XmlElementEventHandler(i2); 
      //srl.UnknownNode += new XmlNodeEventHandler(i3);
      //srl.UnreferencedObject += new UnreferencedObjectEventHandler(i4);

      if (!srl.CanDeserialize(reader))
        throw new ArgumentException("LdRequestDocument.Parse:errror parsing input xml");

      LdRequestDocument tmpDoc = (LdRequestDocument)srl.Deserialize(reader);

      if (tmpDoc.ResponseOrder == LdResponseOrderEnum.Sequential)
        throw new LdException("sequential processing currentry unsupported, please use Unordered");

      return tmpDoc;

    }





    /// <summary>
    /// parses DSML document from input <c>XmlReader</c>
    /// </summary>
    /// <param name="data">input data in XmlReader on specific position</param>
    /// <returns></returns>
    public static LdRequestDocument Parse(XmlReader reader)
    {

      XmlSerializer srl = new XmlSerializer(typeof(LdRequestDocument), "urn:oasis:names:tc:DSML:2:0:core");

      //if (!srl.CanDeserialize(reader))
      //  throw new ArgumentException("errror parsing LdRequestDocument");

      LdRequestDocument tmpDoc = (LdRequestDocument)srl.Deserialize(reader);

      return tmpDoc;

    }






    //private static void i1(object o, XmlAttributeEventArgs e)
    //{ }

    //private static void i2(object o, XmlElementEventArgs e)
    //{ }

    //private static void i3(object o, XmlNodeEventArgs e)
    //{ }

    //private static void i4(object o, UnreferencedObjectEventArgs e)
    //{ }






    protected static void validateDSML(XmlDocument doc)
    {
      //validate against xsd
      XmlSchemaSet schemaSet = new XmlSchemaSet();
      schemaSet.Add("urn:oasis:names:tc:DSML:2:0:core", "https://www.oasis-open.org/committees/dsml/docs/DSMLv2.xsd");
      //schemaSet.Add("urn:oasis:names:tc:DSML:2:0:core", new Uri("../../DSMLv2.xsd", UriKind.Relative).ToString());


      string validateErrMsg = null;

      //this throws exception when invalid doc
      doc.Schemas = schemaSet;
      doc.Validate(new ValidationEventHandler(delegate(object sender, ValidationEventArgs e) { validateErrMsg = e.Message; }));

      if (!String.IsNullOrEmpty(validateErrMsg))
        throw new LdInvalidDocumentException(validateErrMsg);
    }




    //protected ArrayList<string> reqOrder { get; set; }


    //public LdRequest[] ToArray()
    //{
    //  return Requests.ToArray<LdRequest>();
    //}



  }

}